/*
 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
 *
 * This software is open source.
 * See the bottom of this file for the licence.
 */

package org.dom4j.io;

/**
 * <p> <code>OutputFormat</code> represents the format configuration used by
 * {@linkXMLWriter}and its base classes to format the XML output </p>
 * 
 * @author <a href="mailto:james.strachan@metastuff.com">James Strachan </a>
 * @version $Revision: 1.2 $
 */
public class OutputFormat implements Cloneable
{
   /** standard value to indent by, if we are indenting */
   protected static final String STANDARD_INDENT = "  ";

   /**
    * Whether or not to suppress the XML declaration - default is
    * <code>false</code>
    */
   private boolean suppressDeclaration = false;

   /**
    * Whether or not to print new line after the XML declaration - default is
    * <code>true</code>
    */
   private boolean newLineAfterDeclaration = true;

   /** The encoding format */
   private String encoding = "UTF-8";

   /**
    * Whether or not to output the encoding in the XML declaration - default is
    * <code>false</code>
    */
   private boolean omitEncoding = false;

   /** The default indent is no spaces (as original document) */
   private String indent = null;

   /**
    * Whether or not to expand empty elements to &lt;tagName&gt;&lt;/tagName&gt; -
    * default is <code>false</code>
    */
   private boolean expandEmptyElements = false;

   /**
    * The default new line flag, set to do new lines only as in original
    * document
    */
   private boolean newlines = false;

   /** New line separator */
   private String lineSeparator = "\n";

   /** should we preserve whitespace or not in text nodes? */
   private boolean trimText = false;

   /** pad string-element boundaries with whitespace */
   private boolean padText = false;

   /** Whether or not to use XHTML standard. */
   private boolean doXHTML = false;

   /**
    * Controls when to output a line.separtor every so many tags in case of no
    * lines and total text trimming.
    */
   private int newLineAfterNTags = 0; // zero means don't bother.

   /** Quote character to use when writing attributes. */
   private char attributeQuoteChar = '\"';

   /**
    * Creates an <code>OutputFormat</code> with no additional whitespace
    * (indent or new lines) added. The whitespace from the element text content
    * is fully preserved.
    */
   public OutputFormat()
   {
   }

   /**
    * Creates an <code>OutputFormat</code> with the given indent added but no
    * new lines added. All whitespace from element text will be included.
    * 
    * @param indent is the indent string to be used for indentation (usually a
    *           number of spaces).
    */
   public OutputFormat(String indent)
   {
      this.indent = indent;
   }

   /**
    * Creates an <code>OutputFormat</code> with the given indent added with
    * optional newlines between the Elements. All whitespace from element text
    * will be included.
    * 
    * @param indent is the indent string to be used for indentation (usually a
    *           number of spaces).
    * @param newlines whether new lines are added to layout the
    */
   public OutputFormat(String indent, boolean newlines)
   {
      this.indent = indent;
      this.newlines = newlines;
   }

   /**
    * Creates an <code>OutputFormat</code> with the given indent added with
    * optional newlines between the Elements and the given encoding format.
    * 
    * @param indent is the indent string to be used for indentation (usually a
    *           number of spaces).
    * @param newlines whether new lines are added to layout the
    * @param encoding is the text encoding to use for writing the XML
    */
   public OutputFormat(String indent, boolean newlines, String encoding)
   {
      this.indent = indent;
      this.newlines = newlines;
      this.encoding = encoding;
   }

   public String getLineSeparator()
   {
      return lineSeparator;
   }

   /**
    * <p> This will set the new-line separator. The default is <code>\n</code>.
    * Note that if the "newlines" property is false, this value is irrelevant.
    * To make it output the system default line ending string, call
    * <code>setLineSeparator(System.getProperty("line.separator"))</code> </p>
    * 
    * @param separator <code>String</code> line separator to use.
    * 
    * @see #setNewlines(boolean)
    */
   public void setLineSeparator(String separator)
   {
      lineSeparator = separator;
   }

   public boolean isNewlines()
   {
      return newlines;
   }

   /**
    * DOCUMENT ME!
    * 
    * @param newlines <code>true</code> indicates new lines should be printed,
    *           else new lines are ignored (compacted).
    * 
    * @see #setLineSeparator(String)
    */
   public void setNewlines(boolean newlines)
   {
      this.newlines = newlines;
   }

   public String getEncoding()
   {
      return encoding;
   }

   /**
    * DOCUMENT ME!
    * 
    * @param encoding encoding format
    */
   public void setEncoding(String encoding)
   {
      if(encoding != null)
      {
         this.encoding = encoding;
      }
   }

   public boolean isOmitEncoding()
   {
      return omitEncoding;
   }

   /**
    * <p> This will set whether the XML declaration (<code>&lt;?xml version="1.0"
    * encoding="UTF-8"?&gt;</code>)
    * includes the encoding of the document. It is common to suppress this in
    * protocols such as WML and SOAP. </p>
    * 
    * @param omitEncoding <code>boolean</code> indicating whether or not the
    *           XML declaration should indicate the document encoding.
    */
   public void setOmitEncoding(boolean omitEncoding)
   {
      this.omitEncoding = omitEncoding;
   }

   /**
    * <p> This will set whether the XML declaration (<code>&lt;?xml version="1.0"
    * encoding="UTF-8"?&gt;</code>)
    * is included or not. It is common to suppress this in protocols such as WML
    * and SOAP. </p>
    * 
    * @param suppressDeclaration <code>boolean</code> indicating whether or
    *           not the XML declaration should be suppressed.
    */
   public void setSuppressDeclaration(boolean suppressDeclaration)
   {
      this.suppressDeclaration = suppressDeclaration;
   }

   /**
    * DOCUMENT ME!
    * 
    * @return true if the output of the XML declaration (<code>&lt;?xml
    *         version="1.0"?&gt;</code>)
    *         should be suppressed else false.
    */
   public boolean isSuppressDeclaration()
   {
      return suppressDeclaration;
   }

   /**
    * <p> This will set whether a new line is printed after the XML declaration
    * (assuming it is not supressed.) </p>
    * 
    * @param newLineAfterDeclaration <code>boolean</code> indicating whether
    *           or not to print new line following the XML declaration. The
    *           default is true.
    */
   public void setNewLineAfterDeclaration(boolean newLineAfterDeclaration)
   {
      this.newLineAfterDeclaration = newLineAfterDeclaration;
   }

   /**
    * DOCUMENT ME!
    * 
    * @return true if a new line should be printed following XML declaration
    */
   public boolean isNewLineAfterDeclaration()
   {
      return newLineAfterDeclaration;
   }

   public boolean isExpandEmptyElements()
   {
      return expandEmptyElements;
   }

   /**
    * <p> This will set whether empty elements are expanded from
    * <code>&lt;tagName&gt;</code> to
    * <code>&lt;tagName&gt;&lt;/tagName&gt;</code>. </p>
    * 
    * @param expandEmptyElements <code>boolean</code> indicating whether or
    *           not empty elements should be expanded.
    */
   public void setExpandEmptyElements(boolean expandEmptyElements)
   {
      this.expandEmptyElements = expandEmptyElements;
   }

   public boolean isTrimText()
   {
      return trimText;
   }

   /**
    * <p> This will set whether the text is output verbatim (false) or with
    * whitespace stripped as per <code>{@link
    * org.dom4j.Element#getTextTrim()}</code>.
    * </p>
    * 
    * <p> </p>
    * 
    * <p> Default: false </p>
    * 
    * @param trimText <code>boolean</code> true=>trim the whitespace,
    *           false=>use text verbatim
    */
   public void setTrimText(boolean trimText)
   {
      this.trimText = trimText;
   }

   public boolean isPadText()
   {
      return padText;
   }

   /**
    * <p> Ensure that text immediately preceded by or followed by an element
    * will be "padded" with a single space. This is used to allow make
    * browser-friendly HTML, avoiding trimText's transformation of, e.g.,
    * <code>The quick &lt;b&gt;brown&lt;/b&gt; fox</code> into <code>The
    * quick&lt;b&gt;brown&lt;/b&gt;fox</code>
    * (the latter will run the three separate words together into a single
    * word). This setting is not too useful if you haven't also called
    * {@link #setTrimText}. </p>
    * 
    * <p> The padding string will only be added if the text itself starts or
    * ends with some whitespace characters. </p>
    * 
    * <p> Default: false </p>
    * 
    * @param padText <code>boolean</code> if true, pad string-element
    *           boundaries
    */
   public void setPadText(boolean padText)
   {
      this.padText = padText;
   }

   public String getIndent()
   {
      return indent;
   }

   /**
    * <p> This will set the indent <code>String</code> to use; this is usually
    * a <code>String</code> of empty spaces. If you pass null, or the empty
    * string (""), then no indentation will happen. </p> Default: none (null)
    * 
    * @param indent <code>String</code> to use for indentation.
    */
   public void setIndent(String indent)
   {
      // nullify empty string to void unnecessary indentation code
      if((indent != null) && (indent.length() <= 0))
      {
         indent = null;
      }

      this.indent = indent;
   }

   /**
    * Set the indent on or off. If setting on, will use the value of
    * STANDARD_INDENT, which is usually two spaces.
    * 
    * @param doIndent if true, set indenting on; if false, set indenting off
    */
   public void setIndent(boolean doIndent)
   {
      if(doIndent)
      {
         this.indent = STANDARD_INDENT;
      }
      else
      {
         this.indent = null;
      }
   }

   /**
    * <p> This will set the indent <code>String</code>'s size; an indentSize
    * of 4 would result in the indention being equivalent to the
    * <code>String</code> "&nbsp;&nbsp;&nbsp;&nbsp;" (four space characters).
    * </p>
    * 
    * @param indentSize <code>int</code> number of spaces in indentation.
    */
   public void setIndentSize(int indentSize)
   {
      StringBuffer indentBuffer = new StringBuffer();

      for(int i = 0; i < indentSize; i++)
      {
         indentBuffer.append(" ");
      }

      this.indent = indentBuffer.toString();
   }

   /**
    * <p> Whether or not to use the XHTML standard: like HTML but passes an XML
    * parser with real, closed tags. Also, XHTML CDATA sections will be output
    * with the CDATA delimiters: ( &quot; <b>&lt;![CDATA[ </b>&quot; and &quot;
    * <b>]]&gt; </b>&quot; ) otherwise, the class HTMLWriter will output the
    * CDATA text, but not the delimiters. </p>
    * 
    * <p> Default is <code>false</code> </p>
    * 
    * @return DOCUMENT ME!
    */
   public boolean isXHTML()
   {
      return doXHTML;
   }

   /**
    * <p> This will set whether or not to use the XHTML standard: like HTML but
    * passes an XML parser with real, closed tags. Also, XHTML CDATA sections
    * will be output with the CDATA delimiters: ( &quot; <b>&lt;[CDATA[
    * </b>&quot; and &quot; <b>]]&lt; </b>) otherwise, the class HTMLWriter will
    * output the CDATA text, but not the delimiters. </p>
    * 
    * <p> Default: false </p>
    * 
    * @param xhtml <code>boolean</code> true=>conform to XHTML, false=>conform
    *           to HTML, can have unclosed tags, etc.
    */
   public void setXHTML(boolean xhtml)
   {
      doXHTML = xhtml;
   }

   public int getNewLineAfterNTags()
   {
      return newLineAfterNTags;
   }

   /**
    * Controls output of a line.separator every tagCount tags when isNewlines is
    * false. If tagCount equals zero, it means don't do anything special. If
    * greater than zero, then a line.separator will be output after tagCount
    * tags have been output. Used when you would like to squeeze the html as
    * much as possible, but some browsers don't like really long lines. A tag
    * count of 10 would produce a line.separator in the output after 10 close
    * tags (including single tags).
    * 
    * @param tagCount DOCUMENT ME!
    */
   public void setNewLineAfterNTags(int tagCount)
   {
      newLineAfterNTags = tagCount;
   }

   public char getAttributeQuoteCharacter()
   {
      return attributeQuoteChar;
   }

   /**
    * Sets the character used to quote attribute values. The specified character
    * must be a valid XML attribute quote character, otherwise an
    * <code>IllegalArgumentException</code> will be thrown.
    * 
    * @param quoteChar The character to use when quoting attribute values.
    * 
    * @throws IllegalArgumentException If the specified character is not a valid
    *            XML attribute quote character.
    */
   public void setAttributeQuoteCharacter(char quoteChar)
   {
      if((quoteChar == '\'') || (quoteChar == '"'))
      {
         attributeQuoteChar = quoteChar;
      }
      else
      {
         throw new IllegalArgumentException("Invalid attribute quote "
            + "character (" + quoteChar + ")");
      }
   }

   /**
    * Parses command line arguments of the form <code>-omitEncoding
    * -indentSize 3 -newlines -trimText</code>
    * 
    * @param args is the array of command line arguments
    * @param i is the index in args to start parsing options
    * 
    * @return the index of first parameter that we didn't understand
    */
   public int parseOptions(String[] args, int i)
   {
      for(int size = args.length; i < size; i++)
      {
         if(args[i].equals("-suppressDeclaration"))
         {
            setSuppressDeclaration(true);
         }
         else if(args[i].equals("-omitEncoding"))
         {
            setOmitEncoding(true);
         }
         else if(args[i].equals("-indent"))
         {
            setIndent(args[++i]);
         }
         else if(args[i].equals("-indentSize"))
         {
            setIndentSize(Integer.parseInt(args[++i]));
         }
         else if(args[i].startsWith("-expandEmpty"))
         {
            setExpandEmptyElements(true);
         }
         else if(args[i].equals("-encoding"))
         {
            setEncoding(args[++i]);
         }
         else if(args[i].equals("-newlines"))
         {
            setNewlines(true);
         }
         else if(args[i].equals("-lineSeparator"))
         {
            setLineSeparator(args[++i]);
         }
         else if(args[i].equals("-trimText"))
         {
            setTrimText(true);
         }
         else if(args[i].equals("-padText"))
         {
            setPadText(true);
         }
         else if(args[i].startsWith("-xhtml"))
         {
            setXHTML(true);
         }
         else
         {
            return i;
         }
      }

      return i;
   }

   /**
    * A static helper method to create the default pretty printing format. This
    * format consists of an indent of 2 spaces, newlines after each element and
    * all other whitespace trimmed, and XMTML is false.
    * 
    * @return DOCUMENT ME!
    */
   public static OutputFormat createPrettyPrint()
   {
      OutputFormat format = new OutputFormat();
      format.setIndentSize(2);
      format.setNewlines(true);
      format.setTrimText(true);
      format.setPadText(true);

      return format;
   }

   /**
    * A static helper method to create the default compact format. This format
    * does not have any indentation or newlines after an alement and all other
    * whitespace trimmed
    * 
    * @return DOCUMENT ME!
    */
   public static OutputFormat createCompactFormat()
   {
      OutputFormat format = new OutputFormat();
      format.setIndent(false);
      format.setNewlines(false);
      format.setTrimText(true);

      return format;
   }
}

/*
 * Redistribution and use of this software and associated documentation
 * ("Software"), with or without modification, are permitted provided that the
 * following conditions are met:
 * 
 * 1. Redistributions of source code must retain copyright statements and
 * notices. Redistributions must also contain a copy of this document.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * 3. The name "DOM4J" must not be used to endorse or promote products derived
 * from this Software without prior written permission of MetaStuff, Ltd. For
 * written permission, please contact dom4j-info@metastuff.com.
 * 
 * 4. Products derived from this Software may not be called "DOM4J" nor may
 * "DOM4J" appear in their names without prior written permission of MetaStuff,
 * Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
 * 
 * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
 * 
 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
 */
